/*-
 * Copyright (c) 2007-2008 SINA Corporation, All Rights Reserved.
 *  Authors:
 *      Zhu Yan <zhuyan@staff.sina.com.cn>
 *
 * LMTP Database Implementation
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <mysql.h>
#include <errmsg.h>
#include <mysqld_error.h>

#include "libmod.h"
#include "log.h"
#include "lmtp.h"
#include "confparser.h"


#define DBOP_DEFAULT_DB_HOST		"127.0.0.1"
#define DBOP_DEFAULT_DB_USER		"root"
#define DBOP_DEFAULT_DB_PASSWORD	""
#define DBOP_DEFAULT_DB_DBNAME		"test"

#define QUERY_LEN	1023

int mod_init(char *mod_name);
extern int mod_conf_stage_id;
#define DBOP_ERROR	mod_conf_stage_id

static char	conf_dbop_db_host[CONF_ITEM_LEN+1] = DBOP_DEFAULT_DB_HOST;
static char	conf_dbop_db_user[CONF_ITEM_LEN+1] = DBOP_DEFAULT_DB_USER;
static char	conf_dbop_db_password[CONF_ITEM_LEN+1] = DBOP_DEFAULT_DB_PASSWORD;
static char	conf_dbop_db_name[CONF_ITEM_LEN+1] = DBOP_DEFAULT_DB_DBNAME;

static CONF_STR_CONFIG conf_str_array[] = {
	{"db_host",		conf_dbop_db_host},
	{"db_user",		conf_dbop_db_user},
	{"db_password",	conf_dbop_db_password},
	{"db_name",		conf_dbop_db_name},
	{0, 0}
};

#define SAMPLE_LIBRARY_TABLE \
"CREATE TABLE IF NOT EXISTS `msl_%02d%02d%02d` ( \
  `id` bigint(8) unsigned NOT NULL AUTO_INCREMENT, \
  `recv_time` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT 'Mail received time', \
  `ori_ip` int(4) unsigned NOT NULL DEFAULT '0' COMMENT 'Save the original IP in mail body', \
  `rcpt_id` int(4) unsigned DEFAULT '0' COMMENT 'rcpt_id', \
  `mail_path` varchar(255) COLLATE ascii_bin NOT NULL COMMENT 'Save the mail file path', \
  PRIMARY KEY (`id`), \
  KEY `ori_ip` (`ori_ip`), \
  KEY `recv_time` (`recv_time`) \
) ENGINE=MyISAM  DEFAULT CHARSET=ascii COLLATE=ascii_bin AUTO_INCREMENT=1 ;"

static MYSQL *
initialize_mysql(MYSQL *msp)
{
	MYSQL	*msp1;
	MYSQL	*retmysql;
	int		retval;
	uint32_t	timeout = 3;
/*
	if (msp == NULL) {
		msp1 = (MYSQL *)calloc(1, sizeof(MYSQL)); 
		if (msp1 == NULL)
			return NULL;
	} else {
		bzero(msp, sizeof(MYSQL));
		msp1 = msp;
	}
*/
	msp1 = mysql_init(msp);
	if (msp1 == NULL)
		return NULL;

	retval = mysql_options(msp1, 
		MYSQL_OPT_CONNECT_TIMEOUT, 
		(const char *)(&timeout));
	if (retval != 0) {
		WLOG_ERR("Failed to set option: %s", mysql_error(msp1));
		mysql_close(msp1);
/*		free(msp1);*/
		return NULL;
	}

	retval = mysql_options(msp1, MYSQL_SET_CHARSET_NAME, "utf8"); 
	if (retval != 0) {
		WLOG_ERR("Failed to set option: %s", mysql_error(msp1));
		mysql_close(msp1);

/*		free(msp1);*/
		return NULL;
	}

	retmysql = mysql_real_connect(msp1, 
		conf_dbop_db_host, 
		conf_dbop_db_user, 
		conf_dbop_db_password, 
		conf_dbop_db_name, 
		3306, NULL, 0);

	if (retmysql == NULL) {
		WLOG_ERR("Failed to connect: %s", mysql_error(msp1));
/*		free(msp1); */
		mysql_close(msp1);
		return NULL;
	}
	return (msp1);
}

static ssize_t 
db_handler(WTI * wti,CONN_INFO *cip, void *data, void **res)
{
	char		sql_str[QUERY_LEN+1] = "";
	MYSQL		**mspp;
	MYSQL		*msp;
	int			retval;
	LMTP_DATA	*ldp;
	RCPT		*rp;
	int			retry_flag = 0;
	int			err;
	int			sql_len;

	CSF_UNUSED_ARG(cip);
	CSF_UNUSED_ARG(wti);

	if (res == NULL)
		return PIPELINE_FAILED;
	mspp = (MYSQL **)res;
	msp = *mspp;
	if (msp == NULL)
		return PIPELINE_FAILED;

	ldp = (LMTP_DATA *)data;
	STAILQ_FOREACH(rp, &(ldp->mi.rcpt_head), rcpt_entries) {
		sql_len = snprintf(sql_str, QUERY_LEN,
			"INSERT INTO msl (recv_time, ori_ip, rcpt_id, mail_path) \
				VALUES (FROM_UNIXTIME('%d'), '%u', \
					(SELECT rcpt_id from rcpt WHERE rcpt_addr='%s'), '%s');",
			(uint32_t)ldp->mi.time, ldp->mi.ip_addr, rp->rcpt, rp->path);

		for(;;) {
			retval = mysql_real_query(msp, sql_str, sql_len);

			if (retval != 0) {
				err = mysql_errno(msp);
				if (err >= ER_ERROR_FIRST &&
					err <= ER_ERROR_LAST) {
					/* If the SQL syntax error, we won't retry */
					WLOG_ERR("Failed to insert: %s", sql_str);
					rp->rc = DBOP_ERROR;
					break;	
				} else if (err == CR_SERVER_GONE_ERROR ||
						err == CR_SERVER_LOST) {
					/* If the server is disconnected, we reconnect the server */
					WLOG_ERR("MySQL was disconnected!");

					/* Disconnected again, never retry */
					if (retry_flag)
						break;

					/* Reconnect it again */
					msp = initialize_mysql(msp);
					*mspp = msp;
					/* To mark retry once */
					retry_flag = 1;
					if (msp != NULL) {
						/* If we can connect to server, redo the SQL */
						WLOG_INFO("MySQL server reconnected!");
						continue;
					} else {
						/* Could not connect to server yet, Failed! */
						WLOG_ERR("Fatal error: MySQL can not be reconnected!");
						rp->rc = DBOP_ERROR;
						return (PIPELINE_FAILED);
					}
				} else {
					WLOG_ERR("FATAL ERROR TO MySQL!!!");
					rp->rc = DBOP_ERROR;
					return (PIPELINE_FAILED);
				}
			} else {
				break;
			}
		}
		continue;
	}
	return (PIPELINE_CONTINUE);
}

static ssize_t 
db_init(void *arg, void **res)
{
	MYSQL	*msp;
	MYSQL	**mspp;

	CSF_UNUSED_ARG(arg);

	mspp = (MYSQL **)res;

	if (mspp == NULL)
		return (PIPELINE_FAILED);

	msp = initialize_mysql(NULL);
	*mspp = msp;
	if (msp != NULL) {
		WLOG_DEBUG("connect ok [%s][%s][%s]", 
				conf_dbop_db_host, 
				conf_dbop_db_user, 
				conf_dbop_db_name);
		return (PIPELINE_SUCCESS);
	} else {
		WLOG_DEBUG("connect failed! [%s][%s][%s]", 
				conf_dbop_db_host, 
				conf_dbop_db_user, 
				conf_dbop_db_name);
		return (PIPELINE_FAILED);
	}
}

static void 
db_deinit(void *arg, void *res)
{
	MYSQL	*msp;
	CSF_UNUSED_ARG(arg);

	msp = (MYSQL *)res;

	if (msp == NULL)
		return;

	mysql_close(msp);
}

int 
mod_init(char *mod_name)
{
	int r;

	r = load_conf(NULL, mod_name, NULL, conf_str_array);
	if (r != 0)
		WLOG_ERR("load_conf() failed!");

	set_request_init(db_init);
	set_request_deinit(db_deinit);
	set_request_handler(db_handler);

	return (0);
}

